#include <SerialDriver.h>
#include <Asm.h>

SerialDriver::SerialDriver(u16int port) :
  port(port),
  intMode(dec)
{
  outb(port + 1, 0x00);    // Disable all interrupts
  outb(port + 3, 0x80);    // Enable DLAB (set baud rate divisor)
  outb(port + 0, 0x03);    // Set divisor to 3 (lo byte) 38400 baud
  outb(port + 1, 0x00);    //                  (hi byte)
  outb(port + 3, 0x03);    // 8 bits, no parity, one stop bit
  outb(port + 2, 0xC7);    // Enable FIFO, clear them, with 14-byte threshold
  outb(port + 4, 0x0B);    // IRQs enabled, RTS/DSR set
  outb(port + 1, 0x0C);    // enable all interrupts.
}


SerialDriver::~SerialDriver()
{
}

void SerialDriver::put(char c, Colour foreColour, Colour backColour)
{
  if (foreColour != none || backColour != none)
  {
    startColour(foreColour, backColour);
  }
  // Spin until the port is ready
  while (!readyToTransmit())
  {
  }
  
  outb(port, c);
  if (foreColour != none || backColour != none)
  {
    endColour();
  }
}

bool SerialDriver::readyToTransmit()
{
  u8int status = inb(port + 5);
  return (bool) (status & 0x20);
}

//
// write -- Outputs the string c to the monitor, with foreground colour
//          foreColour and background backColour.
//
void SerialDriver::write(char *c, Colour foreColour, Colour backColour)
{
  if (foreColour != none || backColour != none)
  {
    startColour(foreColour, backColour);
  }
  int i = 0;
  while (c[i])
  {
    put(c[i++]);
  }
  if (foreColour != none || backColour != none)
  {
    endColour();
  }
}

//
// writeHex -- Outputs the given signed 32-bit integer to the monitor
//             as a hexidecimal number, prefixed with "0x", with the
//             foreground/background colours specified.
//
void SerialDriver::writeHex(s32int n, Colour foreColour, Colour backColour)
{
  if (foreColour != none || backColour != none)
  {
    startColour(foreColour, backColour);
  }
  s32int tmp;

  write("0x");

  bool noZeroes = true;

  int i;
  for (i = 28; i > 0; i -= 4)
  {
    tmp = (n >> i) & 0xF;
    if (tmp == 0 && noZeroes)
    {
      continue;
    }
    
    if (tmp >= 0xA)
    {
      noZeroes = false;
      put (tmp-0xA+'a');
    }
    else
    {
      noZeroes = false;
      put( tmp+'0');
    }
  }
  
  tmp = n & 0xF;
  if (tmp >= 0xA)
  {
    put (tmp-0xA+'a');
  }
  else
  {
    put( tmp+'0');
  }
  if (foreColour != none || backColour != none)
  {
    endColour();
  }
}

//
// writeDec -- Outputs the given signed 32-bit integer to the monitor
//             as a decimal number, with the
//             foreground/background colours specified.
//
void SerialDriver::writeDec(s32int n, Colour foreColour, Colour backColour)
{
  if (foreColour != none || backColour != none)
  {
    startColour(foreColour, backColour);
  }
  s32int acc = n;
  char c[32];
  int i = 0;
  while (acc > 0)
  {
    c[i] = '0' + acc%10;
    acc /= 10;
    i++;
  }
  c[i] = 0;

  char c2[32];
  c2[i--] = 0;
  int j = 0;
  while(i >= 0)
  {
    c2[i--] = c[j++];
  }
  write(c2);
  if (foreColour != none || backColour != none)
  {
    endColour();
  }
}

//
// operator<< -- Nice syntax for printing strings
//
SerialDriver &SerialDriver::operator<<(char *c)
{
  write(c);
  return *this;
}

//
// operator<< -- Nice syntax for printing strings
//
SerialDriver &SerialDriver::operator<<(const char *c)
{
  write(c);
  return *this;
}

//
// operator<< -- Nice syntax for printing hex integers
//
SerialDriver &SerialDriver::operator<<(u32int i)
{
  if (intMode == dec)
  {
    writeDec(i);
  }
  else
  {
    writeHex(i);
  }
  return *this;
}

//
// operator<< -- Nice syntax for printing hex integers
//
SerialDriver &SerialDriver::operator<<(Special s)
{
  switch(s)
  {
  case endl:
    put('\n');
    break;
  case dec:
    intMode = dec;
    break;
  case hex:
    intMode = hex;
    break;
  }
  return *this;
}

void SerialDriver::startColour(Colour foreColour, Colour backColour)
{
#ifndef SERIAL_DUMB
  write("\e[");
  if (foreColour != none)
    switch(foreColour)
    {
    case black:
      write("30");
      break;
    case red:
      write("31");
      break;
    case green:
      write("32");
      break;
    case yellow:
      write("33");
      break;
    case blue:
      write("34");
      break;
    case magenta:
      write("35");
      break;
    case cyan:
      write("36");
      break;
    case white:
      write("37");
      break;
    case lightRed:
      write("1;31");
      break;
    case lightGreen:
      write("1;32");
      break;
    case lightBlue:
      write("1;34");
      break;
    case lightMagenta:
      write("1;35");
      break;
    case lightCyan:
      write("1;36");
      break;
    default:
      write("1");
    }
  if (backColour != none)
    switch(backColour)
    {
    case black:
      write("40");
      break;
    case red:
      write("41");
      break;
    case green:
      write("42");
      break;
    case yellow:
      write("43");
      break;
    case blue:
      write("44");
      break;
    case magenta:
      write("45");
      break;
    case cyan:
      write("46");
      break;
    case white:
      write("47");
      break;
    }
  write("m");
#endif
}

void SerialDriver::endColour()
{
#ifndef SERIAL_DUMB
  write("\e[0m");
#endif
}
